[Day 3] 我們已經完成 RAG 的第一個重要步驟就是切塊
但這時候系統還有一個問題:
AI 要怎麼知道哪一個 Chunk 跟使用者的問題最相關
例如我們有以下三個 Chunk:
Chunk 1
本產品支援 Windows 10、Windows 11
Chunk 2
本產品支援 Ubuntu 22.04 與 Ubuntu 24.04
Chunk 3
安裝完成後,需要重新啟動系統
使用者問:
「這個產品支援哪些作業系統」
我們希望系統可以找到Chunk 1、Chunk 2
而不是 Chunk 3
但電腦本身並不像人一樣能直接理解語意
因此,我們需要把文字轉換成一種機器比較容易處理的形式
這就是今天的主角:
Embedding
簡單來說:
Embedding 就是把文字、圖片或其他資料,轉換成一組數字組成的向量 (Vector)
例如:「本產品支援 Windows 11」經過 Embedding 轉換成 [0.12, -0.31, 0.82, 0.17, ...]
因此原本的文字會變成一組數值形式的向量特徵
這樣電腦就可以利用數學方法比較不同文字之間的關係
假設我們有:
A:「本產品支援 Windows 11」
B:「Windows 11 是支援的作業系統」
C:「安裝完成後需要重新啟動系統」
人看到之後,很容易知道 A 跟 B 的相關性
但是對電腦來說,這些都只是字串
如果只使用傳統的字串比對:
text1 == text2
那麼「本產品支援 Windows 11」和「Windows 11 是支援的作業系統」並不相同
即使兩句話的意思非常接近,也可能得到 False
這時 Embedding 解決的就是這類問題
假設我們將文字丟進 Embedding Model 模型產生 [0.12, -0.31, 0.82, 0.17, 0.45, ...]
從上面範例就可以知道 A 與 B 的向量可能比較接近
而 C 則可能距離比較遠
因此我們可以利用:
向量相似度 (Vector Similarity)
來判斷兩段文字在語意上的接近程度
Embedding 最有趣的地方是:
它不是單純把文字轉成數字,而是試著把「語意關係」表示在向量空間中
可以把它想像成一張非常巨大的「語意地圖」
把每一個區塊分布在可能會出現的另一個語意區域
因此在向量空間中,相似的內容可能會比較靠近
這就是 Embedding 的核心概念
到了這裡,我們已經知道 Chunk、Embedding
那這些 Vector 要放在哪裡
答案就是:
Vector Database (向量資料庫)
注意:
Vector Database 不只是存 Vector
實際上通常還會保留 Vector + 原始文字 + Metadata
這樣搜尋到 Vector 之後,系統才能知道:
「這個 Vector 原本對應哪一段文件」
這是 RAG 非常重要的一步
因為我們前面把文件轉成向量
當使用者提出問題
系統也會對 User Query 進行 Embedding 在向量資料庫以索引檢索
接著拿這個 Query Vector 去 Vector Database 搜尋
現在資料庫裡面有資料
同時也有使用者問題產生
接著系統計算相似度
根據數值結果排序
因此系統就可以取得相關內容
並將它們交給 LLM
在實際的向量搜尋中,一個常見的方法就是:
Cosine Similarity (餘弦相似度)
它主要用來衡量兩個向量的方向有多接近
簡化來看相似度越高,語意可能越接近
因此系統就可以選擇出相關內容
這就是 RAG 中的:
Retrieval (檢索)
現在可以把 Day 3 與 Day 4 串起來
這才是我們真正想打造的 RAG
今天先使用一個簡單的 Embedding Model 來觀察結果
安裝套件:
pip install sentence-transformers
pip install scikit-learn
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
這是一個常見的 Sentence Embedding Model,可以將句子轉換成向量
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
texts = [
"本產品支援 Windows 10、Windows 11",
"本產品支援 Ubuntu 22.04 與 Ubuntu 24.04",
"安裝完成後,需要重新啟動系統"
]
embeddings = model.encode(texts)
for i, embedding in enumerate(embeddings):
print(f"Text {i + 1}")
print(f"Vector Dimension: {len(embedding)}")
print(embedding)
print()
執行之後可以看到:
Text 1
Vector Dimension: 384
[ 0.01 -0.02 0.04 ... ]
Text 2
Vector Dimension: 384
[ 0.03 -0.01 0.05 ... ]
Text 3
Vector Dimension: 384
[-0.04 0.07 -0.02 ... ]
接著我們可以實際比較與不同文件內容的相似程度
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
query = "這個產品支援哪些作業系統"
documents = [
"本產品支援 Windows 10、Windows 11",
"本產品支援 Ubuntu 22.04 與 Ubuntu 24.04",
"安裝完成後,需要重新啟動系統"
]
query_vector = model.encode([query])
document_vectors = model.encode(documents)
similarities = cosine_similarity(
query_vector,
document_vectors
)[0]
for document, score in zip(documents, similarities):
print(f"{score:.4f} | {document}")
可能得到類似:
0.72 | 本產品支援 Windows 10、Windows 11。
0.69 | 本產品支援 Ubuntu 22.04 與 Ubuntu 24.04。
0.28 | 安裝完成後,需要重新啟動系統。
可以看到:
Windows → 高相似度
Ubuntu → 高相似度
Restart → 低相似度
這就是我們希望 RAG 做的事情
這裡要特別注意 Embedding 的工作不是:
「回答使用者問題」
它主要負責:
「將資料轉換成可以進行語意比較的向量表示」
所以找相關資料跟理解 Context 兩者是不同角色。
既然 Embedding 負責:
「判斷哪些內容比較相關」
那麼 Embedding Model 的品質就會直接影響 Retrieval
那麼即使後面的 LLM 再強,也沒有正確資料可以回答
因此 RAG 的問題就顯得重要
這也是為什麼後面我們需要進一步研究:
今天可以做三個簡單實驗
觀察不同問法是否仍然能找到相同的相關 Chunk
這可以讓我們理解:
Embedding 不只是做關鍵字比對,而是希望捕捉語意上的相似性
觀察:
Chunk 切法改變之後,Retrieval 結果是否也跟著改變
這會讓我們第一次看到:
RAG 的每一個階段,其實都是互相影響的
今天我們把 Day 3 的 Chunking 再往前推了一步:
並理解:
最重要的是,我們終於可以把 RAG 的核心流程串起來
現在我們已經可以讀取並進行 Embedding
但目前只是把 Vector 印在畫面上
真正的 RAG 系統需要把這些資料儲存起來,並且能夠快速搜尋
因此下一步,我們會建立真正的:
Vector Database
讓系統可以做到使用者提問 + 搜尋 + 找到最相關的文件
[Day 5] 我們就來實際建立第一個 Vector Database,讓 AI 助理第一次真正具備「搜尋自己知識庫」的能力